﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Linq;
using System.Net;

namespace GSite.Utils.Mvc
{
    public class SitemapItem
    {
        public bool IsSelected { get; set; }
        public bool IsAlwaysAccessible { get; set; }
        public string Area { get; set; }
        public string Controller { get; set; }
        public string Action { get; set; }
        public string Url { get; set; }
        public string LinkUrl { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public bool NewWindow { get; set; }
        public string[] Roles { get; set; }
        public IEnumerable<SitemapItem> ChildItems { get; set; }
    }

    public static class MenuExtensions
    {
        private static bool IsCurrent(SitemapItem sitemapItem)
        {
            HttpContextBase httpContext = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);

            if (routeData != null && !(routeData.RouteHandler is StopRoutingHandler))
            {
                return
                    ((string)routeData.Values["area"] ?? "") == sitemapItem.Area &&
                    (string)routeData.Values["controller"] == sitemapItem.Controller &&
                    (string)routeData.Values["action"] == sitemapItem.Action;
            }
            else
            {
                return HttpContext.Current.Request.RawUrl.Split('?')[0] == sitemapItem.Url;
            }
        }

        private static bool HasRights(SitemapItem sitemapItem)
        {
            if (sitemapItem.IsAlwaysAccessible)
                return true;

            if (!String.IsNullOrEmpty(sitemapItem.Url))
            {
                return _canAccessUrl(sitemapItem.Url);
            }
            else if (!String.IsNullOrEmpty(sitemapItem.Controller) && !String.IsNullOrEmpty(sitemapItem.Action))
            {
                if (sitemapItem.Roles != null && sitemapItem.Roles.Any())
                {
                    bool hasRightRole = false;

                    foreach (var item in sitemapItem.Roles)
                    {
                        if (HttpContext.Current.User.IsInRole(item))
                            hasRightRole = true;
                    }

                    if (!hasRightRole)
                        return false;
                }
                
                RouteValueDictionary routeValues = new RouteValueDictionary();
                routeValues["controller"] = sitemapItem.Controller;
                routeValues["action"] = sitemapItem.Action;
                routeValues["area"] = sitemapItem.Area;

                return _canAccessRoute(routeValues);
            }
            else
            {
                return false;
            }
        }

        private static IEnumerable<SitemapItem> GetSitemapItems(XElement parent)
        {
            var childItems =
                parent
                .Elements()
                .Select(
                    el => new SitemapItem
                    {
                        IsSelected = false,
                        Area = (string)el.Attribute("area") ?? "",
                        Controller = (string)el.Attribute("controller"),
                        Action = (string)el.Attribute("action"),
                        Url = (string)el.Attribute("url"),
                        Title = WebUtility.HtmlDecode((string)el.Attribute("title")),
                        Description = WebUtility.HtmlDecode((string)el.Attribute("description")),
                        NewWindow = (string)el.Attribute("target") == "_blank",
                        IsAlwaysAccessible = (string)el.Attribute("roles") == "*",
                        Roles = ((string)el.Attribute("roles")).Trim() == string.Empty ? new string[] {}: ((string)el.Attribute("roles")).Split(new char[] {',', ' '}, StringSplitOptions.RemoveEmptyEntries),
                        ChildItems = GetSitemapItems(el)
                    })
                .ToList();

            return childItems;
        }

        private static bool MarkSelectedItems(IEnumerable<SitemapItem> sitemapItems)
        {
            bool hasSelected = false;

            foreach (SitemapItem sitemapItem in sitemapItems)
            {
                if (sitemapItem.ChildItems.Count() == 0)
                {
                    sitemapItem.IsSelected = IsCurrent(sitemapItem);
                }
                else
                {
                    sitemapItem.IsSelected = MarkSelectedItems(sitemapItem.ChildItems);
                }

                if (sitemapItem.IsSelected)
                    hasSelected = true;
            }

            return hasSelected;
        }

        private static IEnumerable<SitemapItem> ProcessSitemapItems(IEnumerable<SitemapItem> sitemapItems)
        {
            HttpContextBase httpContext = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
            UrlHelper urlHelper = new UrlHelper(new RequestContext(httpContext, routeData));

            List<SitemapItem> processedSitemapItems = new List<SitemapItem>();

            foreach (SitemapItem item in sitemapItems)
            {
                IEnumerable<SitemapItem> childItems = ProcessSitemapItems(item.ChildItems);

                if (HasRights(item) || childItems.Any())
                {
                    string linkUrl;

                    if (!String.IsNullOrEmpty(item.Url))
                    {
                        linkUrl = item.Url;
                    }
                    else if (!String.IsNullOrEmpty(item.Controller) && !String.IsNullOrEmpty(item.Action))
                    {
                        linkUrl = urlHelper.Action(item.Action, item.Controller, new { area = item.Area });
                    }
                    else
                    {
                        linkUrl = childItems.First().LinkUrl;
                    }

                    processedSitemapItems.Add(new SitemapItem
                        {
                            IsSelected = item.IsSelected,
                            Area = item.Area,
                            Controller = item.Controller,
                            Action = item.Action,
                            Url = item.Url,
                            LinkUrl = linkUrl,
                            Title = item.Title,
                            Description = item.Description,
                            NewWindow = item.NewWindow,
                            IsAlwaysAccessible = item.IsAlwaysAccessible,
                            Roles = item.Roles,
                            ChildItems = childItems
                        });
                }
            }

            return processedSitemapItems;
        }

        private static IEnumerable<SitemapItem> _sitemapItems;
        private static Func<RouteValueDictionary, bool> _canAccessRoute;
        private static Func<string, bool> _canAccessUrl;
        public static void Initialize(string siteMapVirtualPath, Func<RouteValueDictionary, bool> canAccessRoute, Func<string, bool> canAccessUrl)
        {
            _canAccessRoute = canAccessRoute;
            _canAccessUrl = canAccessUrl;

            string siteMapServerPath = HttpContext.Current.Server.MapPath(siteMapVirtualPath);

            XDocument sitemap = XDocument.Load(siteMapServerPath);

            _sitemapItems = GetSitemapItems(sitemap.Root);
        }

        public static IEnumerable<SitemapItem> GetSitemapItems(this HtmlHelper htmlHelper)
        {
            IEnumerable<SitemapItem> sitemapItems = ProcessSitemapItems(_sitemapItems);

            MarkSelectedItems(sitemapItems);

            return sitemapItems;
        }
    }
}
